/*
 * Copyright (C) 2009 aileron.cc
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package cc.aileron.dao.db;

import static cc.aileron.dao.db.exec.G2DaoExecuteInvoker.Category.*;

import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cc.aileron.accessor.PojoAccessor;
import cc.aileron.accessor.PojoAccessorManager;
import cc.aileron.accessor.PojoAccessorValueNotFoundException;
import cc.aileron.accessor.PojoPropertiesNotFoundException;
import cc.aileron.commons.instance.InstanceManager;
import cc.aileron.commons.resource.ResourceNotFoundException;
import cc.aileron.dao.G2DaoFinder;
import cc.aileron.dao.G2DaoLocal;
import cc.aileron.dao.G2DaoMethod;
import cc.aileron.dao.G2DaoPaging;
import cc.aileron.dao.db.exec.G2DaoExecuteInvoker;
import cc.aileron.dao.db.exec.G2DaoExecutorFactory;
import cc.aileron.dao.db.exec.G2DaoInvoker;
import cc.aileron.dao.db.exec.G2DaoTransactionManager;
import cc.aileron.dao.db.exec.G2DaoExecuteInvoker.Category;
import cc.aileron.dao.db.sql.SqlNotFoundRuntimeException;
import cc.aileron.generic.ObjectContainer;
import cc.aileron.generic.Procedure;
import cc.aileron.template.parser.ParserMethodNotFoundException;
import cc.aileron.template.reader.TemplateSyntaxEexception;

import com.google.inject.Provider;

/**
 * @author Aileron
 * @param <T>
 */
public class G2DaoImpl<T> implements G2DaoLocal<T>
{
    @Override
    public int delete(final T bean)
    {
        return execute(DELETE, bean);
    }

    @Override
    public <Type> G2DaoFinder<T> find(final Type condition)
    {
        return find(condition, null);
    }

    @Override
    public <Type> G2DaoFinder<T> find(final Type condition,
            final Class<Type> cType)
    {
        final G2DbName dbName = this.dbName;
        final PojoAccessorManager manager = this.manager;
        final G2DaoTransactionManager transactionManager = this.transactionManager;
        final Provider<PojoAccessor<T>> provider = this.provider;
        final G2DaoExecuteInvoker<T> executor = this.executor;
        return new G2DaoFinder<T>()
        {
            @Override
            public void bind(final T object)
            {
                if (object == null)
                {
                    throw new IllegalArgumentException("bindable object is null");
                }
                final PojoAccessor<T> accessor = manager.from(object);
                execute(cType, condition, new G2DaoInvoker()
                {
                    @Override
                    public void execute(final ResultSet resultSet)
                            throws SQLException,
                            PojoAccessorValueNotFoundException,
                            PojoPropertiesNotFoundException
                    {
                        final List<String> columnNames = getColumnNames(resultSet);
                        if (!resultSet.next())
                        {
                            return;
                        }
                        bind(accessor, columnNames, resultSet);
                    }
                });
            }

            @Override
            public int count()
            {
                final ObjectContainer<Integer> c = new ObjectContainer<Integer>(0);
                execute(cType, condition, new G2DaoInvoker()
                {
                    @Override
                    public void execute(final ResultSet resultSet)
                            throws SQLException
                    {
                        if (resultSet.next())
                        {
                            c.value = resultSet.getInt(1);
                        }
                    }

                    @Override
                    protected G2DaoMethod method()
                    {
                        return G2DaoMethod.COUNT;
                    }
                });
                return c.value;
            }

            @Override
            public void each(final Procedure<T> procedure)
            {
                execute(cType, condition, new G2DaoInvoker()
                {
                    @Override
                    public void execute(final ResultSet resultSet)
                            throws SQLException,
                            PojoAccessorValueNotFoundException,
                            PojoPropertiesNotFoundException
                    {
                        final List<String> columnNames = getColumnNames(resultSet);
                        while (resultSet.next())
                        {
                            final PojoAccessor<T> accessor = provider.get();
                            bind(accessor, columnNames, resultSet);
                            final T target = accessor.toTarget();
                            procedure.call(target);
                        }
                    }
                });
            }

            @Override
            public void each(final Procedure<T> procedure,
                    final G2DaoPaging paging)
            {
                execute(cType, condition, new G2DaoInvoker()
                {
                    @Override
                    public void execute(final ResultSet resultSet)
                            throws SQLException,
                            PojoAccessorValueNotFoundException,
                            PojoPropertiesNotFoundException
                    {
                        final List<String> columnNames = getColumnNames(resultSet);
                        final int offset = paging.offset();
                        final int size = paging.limit();

                        int i = 0;
                        boolean isNext = offset == 0 ? resultSet.next()
                                : resultSet.absolute(offset);
                        while (isNext)
                        {
                            i += 1;

                            if (size < i)
                            {
                                resultSet.close();
                                break;
                            }

                            final PojoAccessor<T> accessor = provider.get();
                            bind(accessor, columnNames, resultSet);
                            final T target = accessor.toTarget();
                            procedure.call(target);

                            isNext = resultSet.next();
                        }
                    }
                });
            }

            @Override
            public boolean exist()
            {
                final ObjectContainer<Boolean> c = new ObjectContainer<Boolean>(false);
                execute(cType, condition, new G2DaoInvoker()
                {
                    @Override
                    public void execute(final ResultSet resultSet)
                            throws SQLException
                    {
                        c.value = resultSet.next();
                    }
                });
                return c.value;
            }

            @Override
            public List<T> list()
            {
                final ObjectContainer<List<T>> c = new ObjectContainer<List<T>>(new ArrayList<T>(50));
                execute(cType, condition, new G2DaoInvoker()
                {
                    @Override
                    public void execute(final ResultSet resultSet)
                            throws SQLException,
                            PojoAccessorValueNotFoundException,
                            PojoPropertiesNotFoundException
                    {
                        final List<String> columnNames = getColumnNames(resultSet);
                        while (resultSet.next())
                        {
                            final PojoAccessor<T> accessor = provider.get();
                            bind(accessor, columnNames, resultSet);
                            c.value.add(accessor.toTarget());
                        }
                    }
                });
                return c.value;
            }

            @Override
            public List<T> list(final G2DaoPaging paging)
            {
                final ObjectContainer<List<T>> c = new ObjectContainer<List<T>>(new ArrayList<T>(50));
                execute(cType, condition, new G2DaoInvoker()
                {
                    @Override
                    public void execute(final ResultSet resultSet)
                            throws SQLException,
                            PojoAccessorValueNotFoundException,
                            PojoPropertiesNotFoundException
                    {
                        final List<String> columnNames = getColumnNames(resultSet);
                        final int offset = paging.offset();
                        final int size = paging.limit();

                        int i = 0;
                        boolean isNext = offset == 0 ? resultSet.next()
                                : resultSet.absolute(offset);

                        while (isNext)
                        {
                            i += 1;

                            if (size < i)
                            {
                                resultSet.close();
                                break;
                            }

                            final PojoAccessor<T> accessor = provider.get();
                            bind(accessor, columnNames, resultSet);
                            c.value.add(accessor.toTarget());

                            isNext = resultSet.next();
                        }
                    }
                });
                return c.value;
            }

            @Override
            public T one()
            {
                final ObjectContainer<T> c = new ObjectContainer<T>();
                execute(cType, condition, new G2DaoInvoker()
                {
                    @Override
                    public void execute(final ResultSet resultSet)
                            throws SQLException,
                            PojoAccessorValueNotFoundException,
                            PojoPropertiesNotFoundException
                    {
                        final PojoAccessor<T> accessor = provider.get();
                        final List<String> columnNames = getColumnNames(resultSet);
                        if (!resultSet.next())
                        {
                            return;
                        }
                        bind(accessor, columnNames, resultSet);
                        c.value = accessor.toTarget();
                    }
                });
                return c.value;
            }

            /**
             * @param invoker
             * @param condition
             */
            private void execute(final Class<?> type, final Object condition,
                    final G2DaoInvoker invoker)
            {
                if (condition == null)
                {
                    return;
                }
                final Connection connection = transactionManager.get();
                try
                {
                    executor.execute(dbName,
                            connection,
                            type,
                            condition,
                            invoker);
                }
                catch (final SQLException e)
                {
                    logger.error("SQLException", e);
                    throw new Error(e);
                }
                catch (final PojoPropertiesNotFoundException e)
                {
                    logger.error("PojoPropertiesNotFoundException", e);
                    throw new Error(e);
                }
                catch (final PojoAccessorValueNotFoundException e)
                {
                    logger.error("PojoAccessorValueNotFoundException", e);
                    throw new Error(e);
                }
                finally
                {
                    transactionManager.close(connection);
                }
            }
        };
    }

    @Override
    public void initialize(final InstanceManager instance)
    {
        final G2DaoSerial serial = instance.get(G2DaoSerial.class);
        final G2DaoExecutorFactory executorFactory = instance.get(G2DaoExecutorFactory.class);
        final PojoAccessorManager manager = instance.get(PojoAccessorManager.class);
        final Class<T> type = this.type;

        this.dbName = instance.get(G2DbName.class);

        this.manager = manager;
        this.transactionManager = instance.get(G2DaoTransactionManager.class);
        this.executor = executorFactory.get(type, serial, sqlMap, instance);
        this.provider = new Provider<PojoAccessor<T>>()
        {
            @Override
            public PojoAccessor<T> get()
            {
                return manager.from(type);
            }
        };
        try
        {
            execute(INITIALIZE, null);
        }
        catch (final SqlNotFoundRuntimeException e)
        {
            // SQLが存在しない場合は、初期化は行われない
        }
    }

    @Override
    public int insert(final T bean)
    {
        return execute(INSERT, bean);
    }

    @Override
    public String toString()
    {
        return ReflectionToStringBuilder.toString(this);
    }

    @Override
    public int update(final T bean)
    {
        return execute(UPDATE, bean);
    }

    /**
     * @param category
     * @param bean
     * @return count
     */
    private int execute(final Category category, final T bean)
    {
        final Connection connection = transactionManager.get();
        try
        {
            return executor.execute(dbName, category, connection, bean);
        }
        catch (final SQLException e)
        {
            throw new Error(e);
        }
        catch (final PojoAccessorValueNotFoundException e)
        {
            throw new Error(e);
        }
        catch (final PojoPropertiesNotFoundException e)
        {
            throw new Error(e);
        }
        finally
        {
            transactionManager.close(connection);
        }
    }

    /**
     * @param type
     * @param sqlMap
     * @throws ParserMethodNotFoundException
     * @throws TemplateSyntaxEexception
     * @throws ResourceNotFoundException
     * @throws IOException
     */
    public G2DaoImpl(final Class<T> type, final G2DaoSqlMap sqlMap)
            throws IOException, ResourceNotFoundException,
            TemplateSyntaxEexception, ParserMethodNotFoundException
    {
        this.type = type;
        this.sqlMap = sqlMap;
        this.sqlMap.compile(type);
    }

    final Logger logger = LoggerFactory.getLogger(this.getClass());

    private G2DbName dbName;
    private G2DaoExecuteInvoker<T> executor;
    private PojoAccessorManager manager;
    private Provider<PojoAccessor<T>> provider;
    private final G2DaoSqlMap sqlMap;
    private G2DaoTransactionManager transactionManager;
    private final Class<T> type;
}